package net.xo68.coordinator.client;

import com.alipay.sofa.jraft.JRaftUtils;
import com.alipay.sofa.jraft.RouteTable;
import com.alipay.sofa.jraft.conf.Configuration;
import com.alipay.sofa.jraft.entity.PeerId;
import com.alipay.sofa.jraft.option.CliOptions;
import com.alipay.sofa.jraft.rpc.RpcClient;
import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl;
import lombok.extern.slf4j.Slf4j;
import net.xo68.coordinator.core.gid.NewIdRequest;
import net.xo68.coordinator.core.gid.NewIdResponse;

import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * 协调客户端
 * @author wuxie
 * @version 2020-9-29
 */
@Slf4j
public class CoordinatorClient {

    private final CoordinatorClientConfig clientConfig;
    private final CliClientServiceImpl cliClientService;
    private RpcClient rpcClient;
    private CliOptions cliOptions;
    private TreeMap<Long, String> groups = new TreeMap<>();

    public CoordinatorClient(CoordinatorClientConfig clientConfig) {
        this.clientConfig = clientConfig;
        this.cliClientService = new CliClientServiceImpl();
    }

    public void start() throws InterruptedException, TimeoutException{
        cliOptions = new CliOptions();
        cliOptions.setTimeoutMs(clientConfig.getTimeoutMs());
        cliOptions.setMaxRetry(clientConfig.getMaxRetry());
        this.cliClientService.init(cliOptions);
        this.rpcClient = this.cliClientService.getRpcClient();

        Configuration initConf = JRaftUtils.getConfiguration(clientConfig.getCluster());
        RouteTable.getInstance().updateConfiguration(clientConfig.getGroupId(), initConf);
        refreshLeader();
        refreshConf();
    }

    public void shutdown() {
        this.cliClientService.shutdown();
        for (final String groupId : groups.values()) {
            RouteTable.getInstance().removeGroup(groupId);
        }
    }

    private void refreshConf() throws InterruptedException, TimeoutException {
        RouteTable.getInstance().refreshConfiguration(cliClientService, clientConfig.getGroupId(), cliOptions.getRpcDefaultTimeout());
    }

    private void refreshLeader() throws InterruptedException, TimeoutException {
        RouteTable.getInstance().refreshLeader(cliClientService, clientConfig.getGroupId(), cliOptions.getRpcDefaultTimeout());
    }

    public PeerId getLeader(){
        return RouteTable.getInstance().selectLeader(clientConfig.getGroupId());
    }

    public long getNewId(long key){
        NewIdRequest newIdRequest=new NewIdRequest();
        newIdRequest.setIdKey(key);
        try {
            final Object response = this.rpcClient.invokeSync(getLeader().getEndpoint(), newIdRequest,
                    cliOptions.getRpcDefaultTimeout());
            final NewIdResponse newIdResponse = (NewIdResponse) response;
            if(newIdResponse.isStatus()){
                return newIdResponse.getValue();
            }else {
                throw new IllegalStateException("Server error:" + newIdResponse.getError());
            }
        } catch (Exception e) {
            //log.debug(e.getMessage(), e);
            log.warn(e.getMessage(), e);
            try {
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException interruptedException) {
                interruptedException.printStackTrace();
            }
            try {
                refreshLeader();
                return getNewId(key);
            } catch (InterruptedException interruptedException) {
                throw new IllegalStateException("Remoting error:" + interruptedException.getMessage());
            } catch (TimeoutException timeoutException) {
                throw new IllegalStateException("Remoting error:" + timeoutException.getMessage());
            }


        }
    }
}
